using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using GPUAnimationLib.Runtime;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Rendering;
using Object = UnityEngine.Object;

namespace GPUAnimationLib.Baker
{
    /// <summary>
    /// GPU动画烘焙
    /// </summary>
    public class GPUAnimationBaker : MonoBehaviour
    {
        /// <summary>
        /// GPU动画烘焙数据
        /// </summary>
        [Title("GPU动画烘焙数据")] public Data BakerData;

        /// <summary>
        /// Skin
        /// </summary>
        [Title("Skin")] public SkinnedMeshRenderer SkinnedMeshRenderer;

        /// <summary>
        /// 动画组件
        /// </summary>
        [Title("动画组件")] public Animator Animator;

        /// <summary>
        /// 单个顶点受影响的最大骨骼数
        /// </summary>
        [Title("单个顶点受影响的最大骨骼数"), ReadOnly] public int MaxNumberOfBonesPerVertex;

        [Button("刷新单个顶点受影响的最大骨骼数")]
        void RefreshMaxNumberOfBonesPerVertex()
        {
            if (SkinnedMeshRenderer == null || SkinnedMeshRenderer.sharedMesh == null) return;
            MaxNumberOfBonesPerVertex = MeshBakerApi.GetMaxBonesPerVertex(SkinnedMeshRenderer.sharedMesh);
        }

        [Button("GPU动画烘焙")]
        void Internal_Bake()
        {
            Bake("Assets/GameData/GPUAnimationBaker/", null);
        }

        public void Bake(string folder, string tagName)
        {
            if (SkinnedMeshRenderer == null)
                SkinnedMeshRenderer = GetComponentInChildren<SkinnedMeshRenderer>();
            if (Animator == null)
                Animator = GetComponentInChildren<Animator>();
            if (SkinnedMeshRenderer == null || Animator == null)
            {
                Debug.LogError("GPU动画烘焙出错, 请检查GPU动画烘焙数据");
                return;
            }

            GameObject cloneGo = Instantiate(gameObject);
            cloneGo.name = gameObject.name;
            GPUAnimationBaker baker = cloneGo.transform.GetComponent<GPUAnimationBaker>();
            SkinnedMeshRenderer skinnedMeshRenderer = baker.SkinnedMeshRenderer;
            Animator animator = baker.Animator;
            var goBake = GPUAnimationBakerApi.Baker(cloneGo, skinnedMeshRenderer, animator, BakerData,
                $"{folder}{cloneGo.name}", new GPUAnimationBakeEvents()
                {
                    OnMeshRendererBakeEvent = meshRenderer =>
                    {
                        //MeshRenderer设置
                        meshRenderer.shadowCastingMode = ShadowCastingMode.Off;
                        meshRenderer.receiveShadows = false;
                        meshRenderer.lightProbeUsage = LightProbeUsage.Off;
                        meshRenderer.reflectionProbeUsage = ReflectionProbeUsage.Off;
                        meshRenderer.allowOcclusionWhenDynamic = false;
                    },
                    OnMaterialCreateEvent = material =>
                    {
                        //Material设置
                        material.enableInstancing = true;
                    }
                }, tagName);
            if (goBake != null)
                Debug.Log($"烘焙生成GPU动画 : {goBake.name}", goBake);
            DestroyImmediate(cloneGo);
        }
        
        public void Bake(string folder, List<GPUAnimationBakeData> cachedDatas, string tagName)
        {
            if (SkinnedMeshRenderer == null)
                SkinnedMeshRenderer = GetComponentInChildren<SkinnedMeshRenderer>();
            if (Animator == null)
                Animator = GetComponentInChildren<Animator>();
            if (SkinnedMeshRenderer == null || Animator == null)
            {
                Debug.LogError("GPU动画烘焙出错, 请检查GPU动画烘焙数据");
                return;
            }

            GameObject cloneGo = Instantiate(gameObject);
            cloneGo.name = gameObject.name;
            GPUAnimationBaker baker = cloneGo.transform.GetComponent<GPUAnimationBaker>();
            SkinnedMeshRenderer skinnedMeshRenderer = baker.SkinnedMeshRenderer;
            Animator animator = baker.Animator;
            var goBake = GPUAnimationBakerApi.Baker(cloneGo, skinnedMeshRenderer, animator, BakerData,
                $"{folder}{cloneGo.name}", new GPUAnimationBakeEvents()
                {
                    OnMeshRendererBakeEvent = meshRenderer =>
                    {
                        //MeshRenderer设置
                        meshRenderer.shadowCastingMode = ShadowCastingMode.Off;
                        meshRenderer.receiveShadows = false;
                        meshRenderer.lightProbeUsage = LightProbeUsage.Off;
                        meshRenderer.reflectionProbeUsage = ReflectionProbeUsage.Off;
                        meshRenderer.allowOcclusionWhenDynamic = false;
                    },
                    OnMaterialCreateEvent = material =>
                    {
                        //Material设置
                        material.enableInstancing = true;
                    }
                }, cachedDatas, tagName);
            if (goBake != null)
                Debug.Log($"烘焙生成GPU动画 : {goBake.name}", goBake);
            DestroyImmediate(cloneGo);
        }

        [Serializable]
        public class Data
        {
            /// <summary>
            /// 动画数据
            /// </summary>
            [Title("动画数据")] public AnimationData[] AnimationDatas;

            /// <summary>
            /// 附加节点数据
            /// </summary>
            [Title("附加节点数据")] public AttachmentData[] AttachmentDatas;

            /// <summary>
            /// 单个顶点最大支持受多少根骨骼影响
            /// (每增加一根骨骼性能消耗增加约10%)
            /// </summary>
            [Title("单个顶点最大支持受多少根骨骼影响(每增加一根骨骼性能消耗增加约10%)"), Range(1, 4)]
            public int MaxNumberOfBonesPerVertex = 1;

            /// <summary>
            /// 使用的Shader名称
            /// </summary>
            [Title("使用的Shader名称")]
            public string ShaderName = "GPUAnimation/GPUAnimationUnlitAlpha";
        }

        [Serializable]
        public class AttachmentData
        {
            /// <summary>
            /// 附加节点
            /// </summary>
            [Title("附加节点")] public EAttachment Attachment;

            /// <summary>
            /// 附加物挂载的节点
            /// </summary>
            [Title("附加物挂载的节点")] public Transform Transform;
        }
    }

    public struct GPUAnimationBakeEvents
    {
        public Action<MeshRenderer> OnMeshRendererBakeEvent;
        public Action<Material> OnMaterialCreateEvent;
    }
    
    /// <summary>
    /// GPU动画烘焙
    /// </summary>
    public static class GPUAnimationBakerApi
    {
        public static GameObject Baker(GameObject sourceGo, SkinnedMeshRenderer skinnedMeshRenderer, Animator animator,
            GPUAnimationBaker.Data bakerData, string generateFolder, GPUAnimationBakeEvents bakeEvents, string tagName)
        {
#if UNITY_EDITOR
            if (Directory.Exists(generateFolder))
            {
                foreach (var filePath in Directory.GetFiles(generateFolder))
                {
                    if (File.Exists(filePath))
                        File.Delete(filePath);
                }
                Directory.Delete(generateFolder);
            }
            Directory.CreateDirectory(generateFolder);
            UnityEditor.AssetDatabase.Refresh();
            string targetAssetPath = $"{generateFolder}/{sourceGo.name}_GPUAnim.prefab";
            GameObject test = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(targetAssetPath);
            if (test == null)
            {
                GameObject empty = new GameObject(sourceGo.name);
                GameObject meshGo = new GameObject("MainMesh");
                meshGo.transform.parent = empty.transform;
                UnityEditor.PrefabUtility.SaveAsPrefabAsset(empty, targetAssetPath);
                GameObject.DestroyImmediate(empty, allowDestroyingAssets: true);
            }

            GameObject targetGo = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(targetAssetPath);
            string targetGoName = targetGo.name;
            targetGo = GameObject.Instantiate(targetGo);
            targetGo.name = targetGoName;
            targetGo.transform.localScale = sourceGo.transform.localScale;
            GameObject mainMeshGo = targetGo.transform.Find("MainMesh").gameObject;
            RemoveComponent<MeshFilter>(mainMeshGo);
            RemoveComponent<MeshRenderer>(mainMeshGo);
            RemoveComponent<GPUAnimationComponent>(targetGo);
            var gpuAnimationComponent = AddGPUAnimationComponent(targetGo, bakerData);
            //动画矩阵生成
            Texture2D matricesTexture = BoneMatricesBakerApi.BakeAnimationMatricesTexture(skinnedMeshRenderer, animator,
                bakerData, gpuAnimationComponent.AnimationDatas, gpuAnimationComponent.TotalFrames);
            SaveAsset(matricesTexture, generateFolder, sourceGo.name, skinnedMeshRenderer, "MatricesTexture", "asset");
            //Mesh生成
            Mesh newMesh =
                MeshBakerApi.BakeBoneWeightsIntoMesh(skinnedMeshRenderer.sharedMesh,
                    bakerData.MaxNumberOfBonesPerVertex);
            SaveAsset(newMesh, generateFolder, sourceGo.name, skinnedMeshRenderer, "Mesh", "mesh");
            MeshBakerApi.SetMeshReadable(false,
                $"{generateFolder}/{sourceGo.name}_{skinnedMeshRenderer.name}_Mesh.mesh");
            //Attachment数据生成
            var (gpuAttachmentDatas, attachmentAnchorData) = AttachmentBakerApi.BakeAttachmentAnchorData(sourceGo, animator, bakerData,
                gpuAnimationComponent.AnimationDatas, gpuAnimationComponent.TotalFrames);
            gpuAnimationComponent.AttachmentDatas = gpuAttachmentDatas;
            gpuAnimationComponent.AttachmentAnchorData = attachmentAnchorData;
            SaveAsset(attachmentAnchorData, generateFolder, sourceGo.name, skinnedMeshRenderer, "AttachmentAnchorData", "asset");
            
            MeshFilter meshFilter = mainMeshGo.AddComponent<MeshFilter>();
            MeshRenderer meshRenderer = mainMeshGo.AddComponent<MeshRenderer>();
            bakeEvents.OnMeshRendererBakeEvent?.Invoke(meshRenderer);
            Material newMaterial = CreateMaterial(skinnedMeshRenderer, matricesTexture, bakerData.ShaderName);
            bakeEvents.OnMaterialCreateEvent?.Invoke(newMaterial);
            SaveAsset(newMaterial, generateFolder, sourceGo.name, skinnedMeshRenderer, "Material", "mat");
            if (skinnedMeshRenderer.sharedMaterials != null && skinnedMeshRenderer.sharedMaterials.Length > 1) //处理多材质
            {
                Material[] newMaterials = new Material[skinnedMeshRenderer.sharedMaterials.Length];
                newMaterials[0] = newMaterial;
                for (int i = 1; i < skinnedMeshRenderer.sharedMaterials.Length; i++)
                    newMaterials[i] = skinnedMeshRenderer.sharedMaterials[i];
                meshRenderer.sharedMaterials = newMaterials;
            }
            else
                meshRenderer.sharedMaterial = newMaterial;
            meshFilter.sharedMesh = newMesh;
            ProcessTargetGO(ref targetGo, tagName, skinnedMeshRenderer);
            UnityEditor.PrefabUtility.SaveAsPrefabAsset(targetGo, targetAssetPath);
            //UnityEditor.PrefabUtility.UnloadPrefabContents(targetGo);
            GameObject.DestroyImmediate(targetGo);
            return UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(targetAssetPath);
#else
            return null;
#endif
        }

        public static GameObject Baker(GameObject sourceGo, SkinnedMeshRenderer skinnedMeshRenderer, Animator animator,
            GPUAnimationBaker.Data bakerData, string generateFolder, GPUAnimationBakeEvents bakeEvents, List<GPUAnimationBakeData> cachedDatas, string tagName)
        {
#if UNITY_EDITOR
            if (Directory.Exists(generateFolder))
            {
                foreach (var filePath in Directory.GetFiles(generateFolder))
                {
                    if (File.Exists(filePath))
                        File.Delete(filePath);
                }
                Directory.Delete(generateFolder);
            }
            Directory.CreateDirectory(generateFolder);
            UnityEditor.AssetDatabase.Refresh();
            string targetAssetPath = $"{generateFolder}/{sourceGo.name}_GPUAnim.prefab";
            GameObject test = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(targetAssetPath);
            if (test == null)
            {
                GameObject empty = new GameObject(sourceGo.name);
                GameObject meshGo = new GameObject("MainMesh");
                meshGo.transform.parent = empty.transform;
                UnityEditor.PrefabUtility.SaveAsPrefabAsset(empty, targetAssetPath);
                GameObject.DestroyImmediate(empty, allowDestroyingAssets: true);
            }

            GameObject targetGo = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(targetAssetPath);
            string targetGoName = targetGo.name;
            targetGo = GameObject.Instantiate(targetGo);
            targetGo.name = targetGoName;
            targetGo.transform.localScale = sourceGo.transform.localScale;
            GameObject mainMeshGo = targetGo.transform.Find("MainMesh").gameObject;
            RemoveComponent<MeshFilter>(mainMeshGo);
            RemoveComponent<MeshRenderer>(mainMeshGo);
            RemoveComponent<GPUAnimationComponent>(targetGo);
            var gpuAnimationComponent = AddGPUAnimationComponent(targetGo, bakerData);

            Texture2D matricesTexture = null;
            Mesh newMesh = null;
            GPUAttachmentData[] gpuAttachmentDatas = null;
            AttachmentAnchorData attachmentAnchorData = null;
            //查找缓存是否有同网格
            if (TryGetSameGPUAnimationBakeData(skinnedMeshRenderer, animator, cachedDatas, out var gpuAnimationBakeData))
            {
                matricesTexture = gpuAnimationBakeData.BoneMatricesTexture;
                //Mesh生成
                newMesh = MeshBakerApi.BakeBoneWeightsIntoMesh(skinnedMeshRenderer.sharedMesh, bakerData.MaxNumberOfBonesPerVertex);
                string newMeshPath = SaveAsset(newMesh, generateFolder, sourceGo.name, skinnedMeshRenderer, "Mesh", "mesh");
                MeshBakerApi.SetMeshReadable(false,
                    $"{generateFolder}/{sourceGo.name}_{skinnedMeshRenderer.name}_Mesh.mesh");
                //newMesh = gpuAnimationBakeData.Mesh;
                gpuAttachmentDatas = new GPUAttachmentData[gpuAnimationBakeData.AttachmentDatas.Length];
                for (int i = 0; i < gpuAnimationBakeData.AttachmentDatas.Length; i++)
                    gpuAttachmentDatas[i] = gpuAnimationBakeData.AttachmentDatas[i];
                attachmentAnchorData = gpuAnimationBakeData.AttachmentAnchorData;

                string GetRootName(Transform trans)
                {
                    while (trans.parent != null)
                    {
                        trans = trans.parent;
                    }
                    return trans.gameObject.name;
                }
                Debug.Log($"发现相同骨骼动画 : {GetRootName(skinnedMeshRenderer.transform)} [{GetRootName(gpuAnimationBakeData.SkinnedMeshRenderer.transform)}]");
            }
            else
            {
                var (cloneSkin, cloneAnimator) = CloneSkinnedMeshRendererAndAnimator(skinnedMeshRenderer);
                //动画矩阵生成
                matricesTexture = BoneMatricesBakerApi.BakeAnimationMatricesTexture(skinnedMeshRenderer, animator,
                    bakerData, gpuAnimationComponent.AnimationDatas, gpuAnimationComponent.TotalFrames);
                string matricesTexturePath = SaveAsset(matricesTexture, generateFolder, sourceGo.name, skinnedMeshRenderer, "MatricesTexture", "asset");
                //Mesh生成
                newMesh = MeshBakerApi.BakeBoneWeightsIntoMesh(skinnedMeshRenderer.sharedMesh, bakerData.MaxNumberOfBonesPerVertex);
                string newMeshPath = SaveAsset(newMesh, generateFolder, sourceGo.name, skinnedMeshRenderer, "Mesh", "mesh");
                MeshBakerApi.SetMeshReadable(false,
                    $"{generateFolder}/{sourceGo.name}_{skinnedMeshRenderer.name}_Mesh.mesh");
                //Attachment数据生成
                (gpuAttachmentDatas, attachmentAnchorData) = AttachmentBakerApi.BakeAttachmentAnchorData(sourceGo, animator, bakerData,
                    gpuAnimationComponent.AnimationDatas, gpuAnimationComponent.TotalFrames);
                string attachmentAnchorDataPath = SaveAsset(attachmentAnchorData, generateFolder, sourceGo.name, skinnedMeshRenderer, "AttachmentAnchorData", "asset");

                (SkinnedMeshRenderer, Animator) CloneSkinnedMeshRendererAndAnimator(SkinnedMeshRenderer skin)
                {
                    StringBuilder sb = new StringBuilder();
                    Transform trans = skin.transform;
                    List<string> strs = new List<string>(){trans.gameObject.name};
                    while (trans.parent != null)
                    {
                        trans = trans.parent;
                        strs.Add(trans.gameObject.name);
                    }
                    for (int i = strs.Count - 2; i >= 0; i--)
                        sb.Append(i == 0 ? $"{strs[i]}" : $"{strs[i]}/");
                    GameObject go = GameObject.Instantiate(trans.gameObject);
                    go.name = trans.gameObject.name;
                    SkinnedMeshRenderer skinClone = go.transform.Find(sb.ToString()).GetComponent<SkinnedMeshRenderer>();
                    Animator animatorClone = go.GetComponentInChildren<Animator>();
                    return (skinClone, animatorClone);
                }
                gpuAnimationBakeData = new GPUAnimationBakeData()
                {
                    SkinnedMeshRenderer = cloneSkin,
                    Animator = cloneAnimator,
                    BoneMatricesTexture = UnityEditor.AssetDatabase.LoadAssetAtPath<Texture2D>(matricesTexturePath),
                    Mesh = UnityEditor.AssetDatabase.LoadAssetAtPath<Mesh>(newMeshPath),
                    AttachmentDatas = gpuAttachmentDatas,
                    AttachmentAnchorData = UnityEditor.AssetDatabase.LoadAssetAtPath<AttachmentAnchorData>(attachmentAnchorDataPath),
                };
                cachedDatas.Add(gpuAnimationBakeData);
            }
            gpuAnimationComponent.AttachmentDatas = gpuAttachmentDatas;
            gpuAnimationComponent.AttachmentAnchorData = attachmentAnchorData;
            
            MeshFilter meshFilter = mainMeshGo.AddComponent<MeshFilter>();
            MeshRenderer meshRenderer = mainMeshGo.AddComponent<MeshRenderer>();
            bakeEvents.OnMeshRendererBakeEvent?.Invoke(meshRenderer);
            Material newMaterial = CreateMaterial(skinnedMeshRenderer, matricesTexture, bakerData.ShaderName);
            bakeEvents.OnMaterialCreateEvent?.Invoke(newMaterial);
            SaveAsset(newMaterial, generateFolder, sourceGo.name, skinnedMeshRenderer, "Material", "mat");
            if (skinnedMeshRenderer.sharedMaterials != null && skinnedMeshRenderer.sharedMaterials.Length > 1) //处理多材质
            {
                Material[] newMaterials = new Material[skinnedMeshRenderer.sharedMaterials.Length];
                newMaterials[0] = newMaterial;
                for (int i = 1; i < skinnedMeshRenderer.sharedMaterials.Length; i++)
                    newMaterials[i] = skinnedMeshRenderer.sharedMaterials[i];
                meshRenderer.sharedMaterials = newMaterials;
            }
            else
                meshRenderer.sharedMaterial = newMaterial;
            meshFilter.sharedMesh = newMesh;
            ProcessTargetGO(ref targetGo, tagName, skinnedMeshRenderer);
            UnityEditor.PrefabUtility.SaveAsPrefabAsset(targetGo, targetAssetPath);
            //UnityEditor.PrefabUtility.UnloadPrefabContents(targetGo);
            GameObject.DestroyImmediate(targetGo);
            return UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(targetAssetPath);
#else
            return null;
#endif
        }
        
        public static GPUAnimationComponent AddGPUAnimationComponent(GameObject go, GPUAnimationBaker.Data bakerData)
        {
            var component = go.AddComponent<GPUAnimationComponent>();

            component.AnimationDatas = new GPUAnimationData[bakerData.AnimationDatas.Length];
            int currentFrameIndex = 0;
            for (int animationIndex = 0; animationIndex < bakerData.AnimationDatas.Length; animationIndex++)
            {
                AnimationData animationData = bakerData.AnimationDatas[animationIndex];
                var startIndex = currentFrameIndex;
                var length = (int)(animationData.AnimationClip.length * animationData.SampleFrameRate) + 1;
                var loop = animationData.Loop;
                var sampleFrameRate = animationData.SampleFrameRate;
                component.AnimationDatas[animationIndex] = new GPUAnimationData
                {
                    AnimationId = animationIndex,
                    StartIndex = startIndex,
                    Length = length,
                    Loop = loop,
                    SampleFrameRate = sampleFrameRate,
                };
                currentFrameIndex += length;
            }
            component.AnimationNameToId = new Dictionary<string, int>(component.AnimationDatas.Length);
            for (int animationIndex = 0; animationIndex < bakerData.AnimationDatas.Length; animationIndex++)
            {
                AnimationData animationData = bakerData.AnimationDatas[animationIndex];
                component.AnimationNameToId.Add(animationData.AnimatorStateName, animationIndex);
            }
            component.TotalFrames = currentFrameIndex;
            return component;
        }

        public static Material CreateMaterial(SkinnedMeshRenderer skinnedMeshRenderer, Texture2D matricesTexture, string shaderName)
        {
            var material = new Material(Shader.Find(shaderName));
            material.mainTexture = skinnedMeshRenderer.sharedMaterial.mainTexture;
            material.SetTexture("_AnimatedBoneMatrices", matricesTexture);
            material.SetFloat("_SizeWidth", matricesTexture.width);
            material.SetFloat("_SizeHeight", matricesTexture.height);
            return material;
        }

        public static string SaveAsset(Object asset, string folder, string animatorName,
            SkinnedMeshRenderer skinnedMeshRenderer, string assetName, string extension)
        {
            if (asset == null) return null;
            string assetPath = $"{folder}/{animatorName}_{skinnedMeshRenderer.name}_{assetName}.{extension}";
#if UNITY_EDITOR
            UnityEditor.AssetDatabase.CreateAsset(asset, assetPath);
#endif
            return assetPath;
        }

        public static void RemoveComponent<T>(GameObject gameObject) where T : Component
        {
            T component = gameObject.GetComponent<T>();
            if (component != null) Object.DestroyImmediate(component, allowDestroyingAssets: true);
        }

        public static void IterateOverAllFramesThroughAnimator(
            Animator animator, GPUAnimationBaker.Data bakerData, GPUAnimationData[] animationDatas,
            Action<int> actionPerFrame)
        {
            animator.speed = 0;
            int currentFrameIndex = 0;

            for (int animationIndex = 0; animationIndex < bakerData.AnimationDatas.Length; animationIndex++)
            {
                AnimationData animationData = bakerData.AnimationDatas[animationIndex];
                GPUAnimationData gpuAnimationData = animationDatas[animationIndex];

                //动画采样
                for (int frameIndex = 0; frameIndex < gpuAnimationData.Length; frameIndex++)
                {
                    float progressRatio = frameIndex / (gpuAnimationData.Length - 1f);
                    //动画采样
                    animator.Play(animationData.AnimatorStateName, -1, progressRatio);

                    //刷新动画数据
                    animator.Update(0);

                    //采样计算
                    actionPerFrame(currentFrameIndex);

                    currentFrameIndex++;
                }
            }
        }

        public static bool TryGetSameGPUAnimationBakeData(SkinnedMeshRenderer skinnedMeshRenderer, Animator animator, List<GPUAnimationBakeData> cachedDatas, out GPUAnimationBakeData data)
        {
            data = null;
            for (int i = 0; i < cachedDatas.Count; i++)
            {
                var gpuAnimationBakeData = cachedDatas[i];
                if (CheckSkinnedMeshRendererHasSameBones(skinnedMeshRenderer, gpuAnimationBakeData.SkinnedMeshRenderer) && CheckIsSameAnimator(animator, gpuAnimationBakeData.Animator))
                {
                    data = gpuAnimationBakeData;
                    break;
                }
            }
            return data != null;
        }
        
        public static bool CheckSkinnedMeshRendererHasSameBones(SkinnedMeshRenderer skinnedMeshRendererA,
            SkinnedMeshRenderer skinnedMeshRendererB)
        {
            static bool CheckMatrixEquality(Matrix4x4 m1, Matrix4x4 m2)
            {
                return m1.m00 == m2.m00 && m1.m01 == m2.m01 && m1.m02 == m2.m02 && m1.m03 == m2.m03
                       && m1.m10 == m2.m10 && m1.m11 == m2.m11 && m1.m12 == m2.m12 && m1.m13 == m2.m13
                       && m1.m20 == m2.m20 && m1.m21 == m2.m21 && m1.m22 == m2.m22 && m1.m23 == m2.m23
                       && m1.m30 == m2.m30 && m1.m31 == m2.m31 && m1.m32 == m2.m32 && m1.m33 == m2.m33;
            }

            bool hasSameBones = false;
            if (skinnedMeshRendererA.bones.Length == skinnedMeshRendererB.bones.Length)
            {
                hasSameBones = true;
                for (int i = 0; i < skinnedMeshRendererA.bones.Length; i++)
                {
                    if (!CheckMatrixEquality(skinnedMeshRendererA.bones[i].localToWorldMatrix,
                            skinnedMeshRendererB.bones[i].localToWorldMatrix))
                    {
                        hasSameBones = false;
                        break;
                    }

                    if (!CheckMatrixEquality(skinnedMeshRendererA.sharedMesh.bindposes[i],
                            skinnedMeshRendererB.sharedMesh.bindposes[i]))
                    {
                        hasSameBones = false;
                        break;
                    }
                }
            }

            return hasSameBones;
        }

        public static bool CheckIsSameAnimator(Animator animatorA, Animator animatorB)
        {
            (HashSet<string>, Dictionary<string, AnimationClip>) GetInfo(Animator animator)
            {
                Dictionary<string, AnimationClip> allClips = new Dictionary<string, AnimationClip>();
                foreach (var animationClip in animator.runtimeAnimatorController.animationClips)
                {
                    if (!allClips.ContainsKey(animationClip.name))
                        allClips.Add(animationClip.name, animationClip);
                }

                HashSet<string> stateSet = new HashSet<string>();
#if UNITY_EDITOR
                if (animator.runtimeAnimatorController is UnityEditor.Animations.AnimatorController animatorController)
                {
                    foreach (var layer in animatorController.layers)
                    {
                        foreach (var state in layer.stateMachine.states)
                        {
                            if (state.state.motion == null) continue;
                            if (state.state.motion.name.ToLower() == "ani_apose" ||
                                state.state.name.ToLower() == "ani_apose") continue; //APose不烘焙
                            stateSet.Add(state.state.name);
                        }
                    }
                }
#endif
                return (stateSet, allClips);
            }

            bool IsSameState(HashSet<string> stateA, HashSet<string> stateB)
            {
                if (stateA.Count != stateB.Count)
                    return false;
                foreach (var state in stateA)
                {
                    if (!stateB.Contains(state))
                        return false;
                }
                return true;
            }

            bool IsSameClips(Dictionary<string, AnimationClip> clipsA, Dictionary<string, AnimationClip> clipsB)
            {
                bool IsSameClip(AnimationClip clipA, AnimationClip clipB)
                {
                    if (clipA.name != clipB.name) return false;
                    if (Math.Abs(clipA.length - clipB.length) > 0.001f) return false;
                    if (clipA.isLooping != clipB.isLooping) return false;
                    return true;
                }
                
                if (clipsA.Count != clipsB.Count)
                    return false;
                foreach (var item in clipsA)
                {
                    if (!clipsB.ContainsKey(item.Key))
                        return false;
                    if (!IsSameClip(item.Value, clipsB[item.Key]))
                        return false;
                }
                return true;
            }
            var (stateSetA, allClipsA) = GetInfo(animatorA);
            var (stateSetB, allClipsB) = GetInfo(animatorB);
            return IsSameState(stateSetA, stateSetB) && IsSameClips(allClipsA, allClipsB);
        }

        public static void ProcessTargetGO(ref GameObject targetGO, string tagName, SkinnedMeshRenderer skinnedMeshRenderer)
        {
            GameObject go = new GameObject(targetGO.name);
            targetGO.name = "Root";
            targetGO.transform.parent = go.transform;
            targetGO = go;
            var mainMesh = targetGO.transform.Find("Root/MainMesh");
            var boundsComponent = mainMesh.gameObject.AddComponent<BoundsComponent>();
            boundsComponent.Extent = skinnedMeshRenderer.bounds.extents;
            if (!string.IsNullOrEmpty(tagName))
                SetTagRecursively(targetGO, tagName);
            SetLayerRecursively(targetGO, LayerMask.NameToLayer("3D"));
        }
        
        public static void SetLayerRecursively(GameObject obj, int layer)
        {
            obj.layer = layer;
            foreach (Transform child in obj.transform)
                SetLayerRecursively(child.gameObject, layer);
        }
        public static void SetTagRecursively(GameObject obj, string tag)
        {
            obj.tag = tag;
            foreach (Transform child in obj.transform)
                SetTagRecursively(child.gameObject, tag);
        }
    }
    
    public class GPUAnimationBakeData
    {
        public SkinnedMeshRenderer SkinnedMeshRenderer;
        public Animator Animator;
        public Texture2D BoneMatricesTexture;
        public Mesh Mesh;
        public GPUAttachmentData[] AttachmentDatas;
        public AttachmentAnchorData AttachmentAnchorData;

        public void Dispose()
        {
            if (SkinnedMeshRenderer != null)
            {
                Transform trans = SkinnedMeshRenderer.transform;
                while (trans.parent != null)
                    trans = trans.parent;
                GameObject.DestroyImmediate(trans.gameObject);
                SkinnedMeshRenderer = null;
            }
        }
    }
}